package com.wetsion.securityoauthdemo.rmsclient.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wetsion.securityoauthdemo.rmsclient.common.*;
import com.wetsion.securityoauthdemo.rmsclient.handler.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

/**
 * rms-sdk 核心过滤器，
 * 提供了在请求外层提供了获取auth认证和校验认证，
 * 成功和失败处理提供了插口，只需自定义实现并实例成bean
 * 获取用户信息也提供了插口，只需自定义实现并实例成bean
 *
 * @author weixin
 * @version 1.0
 * @CLassName RmsOauthFilter
 * @date 2019/3/8 2:12 PM
 */
@Order(100)
public class RmsOauthFilter implements Filter {

    private final static Logger logger = LoggerFactory.getLogger(RmsOauthFilter.class);

    @Autowired
    RmsClientDetailsProperties rmsClientDetailsProperties;

    private AuthenticationFailureHandler authenticationFailureHandler
            = new DefaultAuthenticationFailureHandler();

    private AuthenticationSuccessHandler authenticationSuccessHandler
            = new DefaultAuthenticationSuccessHandler();

    private UserDetailsGetter userDetailsGetter = new DefaultUserDetailsGetter();

    private ClientDetailsGetter clientDetailsGetter = new DefaultClientDetailsGetter();;

    @Autowired
    RmsOauthClientContext rmsOauthClientContext;

    private UserDetails userDetails;

    private ClientDetails clientDetails;

    public RmsOauthFilter() {}

    public RmsOauthFilter(AuthenticationFailureHandler authenticationFailureHandler,
                          AuthenticationSuccessHandler authenticationSuccessHandler,
                          UserDetailsGetter userDetailsGetter) {
        this.authenticationFailureHandler = authenticationFailureHandler;
        this.authenticationSuccessHandler = authenticationSuccessHandler;
        this.userDetailsGetter = userDetailsGetter;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        logger.info("entered rms oauth filter");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        try {
            userDetails = userDetailsGetter.handle(request, response);
            clientDetails = clientDetailsGetter.handle(request, response, rmsClientDetailsProperties);
            RmsOauthAccessToken accessToken = handleAccessToken();
            if (Objects.isNull(accessToken)) {
                logger.info("认证失败: token为空");
                failureHandle(request, response);
            } else {
                logger.info("获取token成功，token: {}", accessToken.getAccessToken());
                // 去auth server校验token是否有效
                JSONObject checkResult = remoteCheckToken(accessToken.getAccessToken());
                if (Objects.nonNull(checkResult)) {
                    if (checkResult.containsKey(RmsOauthConstant.ACTIVE) || checkResult.getBoolean(RmsOauthConstant.ACTIVE)) {
                        // 最终校验成功，active为true
                        successHandle(request, response, filterChain);
                    } else {
                        logger.info("token 校验失败，校验结果: {}", checkResult.toJSONString());
                        failureHandle(request, response);
                    }
                } else {
                    logger.info("token 校验异常，校验结果为空");
                    failureHandle(request, response);
                }
            }
        } catch (Exception e) {
            logger.info("获取认证失败: {}", e.getMessage());
            failureHandle(request, response);
        }
    }

    private void successHandle(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        authenticationSuccessHandler.onAuthenticationSuccess(request, response, filterChain);
    }

    private void failureHandle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        authenticationFailureHandler.onAuthenticationFailure(request, response);
    }

    /**
     * 先从本地上下文获取token，进行判断
     **/
    private RmsOauthAccessToken handleAccessToken() throws Exception {
        // 先从上下文中获取accessToken
        RmsOauthAccessToken rmsOauthAccessToken = rmsOauthClientContext.getAccessToken();
        if (Objects.isNull(rmsOauthAccessToken) || rmsOauthAccessToken.isExpired()) {
            try {
                RmsAccessTokenRequest rmsAccessTokenRequest = rmsOauthClientContext.getAccessTokenRequest();
                if (Objects.isNull(rmsAccessTokenRequest)) {
                    throw new Exception("No OAuth 2 security context has been established. Unable to access rms");
                }
                String stateKey = rmsAccessTokenRequest.getStateKey();
                if (stateKey != null) {
                    rmsAccessTokenRequest.setPreservedState(rmsOauthClientContext.removePreservedState(stateKey));
                }

                RmsOauthAccessToken existingToken = rmsOauthClientContext.getAccessToken();
                if (existingToken != null) {
                    rmsAccessTokenRequest.setExistingToken(existingToken);
                }
//                RmsOauthAccessToken accessToken = null;
                rmsOauthAccessToken = obtainAccessToken();
                if (rmsOauthAccessToken == null || rmsOauthAccessToken.getValue() == null) {
                    throw new IllegalStateException(
                            "Access token provider returned a null access token, which is illegal according to the contract.");
                }
                rmsOauthClientContext.setAccessToken(rmsOauthAccessToken);
            } catch (Exception e) {
                throw e;
            }
        }
        return rmsOauthAccessToken;
    }

    /**
     * 从远程auth server获取token认证
     * @return com.wetsion.securityoauthdemo.rmsclient.common.RmsOauthAccessToken
     **/
    private RmsOauthAccessToken obtainAccessToken() {
        RmsOauthAccessToken accessToken = null;
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add(
                new BasicAuthenticationInterceptor(rmsClientDetailsProperties.getClientId(), rmsClientDetailsProperties.getClientSecret()));
        MultiValueMap<String, String> params= new LinkedMultiValueMap<>();
        params.add(RmsOauthConstant.GRANT_TYPE, rmsClientDetailsProperties.getGrantType());
        params.add(RmsOauthConstant.USERNAME, userDetails.getUsername());
        params.add(RmsOauthConstant.PASSWORD, userDetails.getPassword());
        params.add(RmsOauthConstant.CLIENT_ID, clientDetails.getClientId());
        params.add(RmsOauthConstant.CLIENT_SECRET, clientDetails.getClientSecret());
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<String> responseEntity = null;
        try {
            responseEntity = restTemplate.postForEntity(
                    rmsClientDetailsProperties.getAccessTokenUrl(), entity, String.class);
            accessToken = JSON.parseObject(responseEntity.getBody(), RmsOauthAccessToken.class);
        } catch (Exception e) {
            logger.info("从远程auth获取认证失败: {}", e.getMessage());
        }
        return accessToken;
    }

    /**
     * 去远程auth server check_token接口校验当前token是否有效
     **/
    private JSONObject remoteCheckToken(String accessToken) {
        JSONObject result = null;
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add(
                new BasicAuthenticationInterceptor(rmsClientDetailsProperties.getClientId(), rmsClientDetailsProperties.getClientSecret()));
        MultiValueMap<String, String> params= new LinkedMultiValueMap<>();
        params.add(RmsOauthConstant.TOKEN, accessToken);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<String> responseEntity;
        try {
            responseEntity = restTemplate.postForEntity(
                    rmsClientDetailsProperties.getCheckTokenUrl(), entity, String.class);
            result = JSON.parseObject(responseEntity.getBody());
        } catch (Exception e) {
            logger.info("从远程auth校验认证: {} 失败: {}", accessToken, e.getMessage());
        }
        return result;
    }

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
        this.authenticationSuccessHandler = authenticationSuccessHandler;
    }

    public void setClientDetailsGetter(ClientDetailsGetter clientDetailsGetter) {
        this.clientDetailsGetter = clientDetailsGetter;
    }

    public void setUserDetailsGetter(UserDetailsGetter userDetailsGetter) {
        this.userDetailsGetter = userDetailsGetter;
    }
}
